package ncme.auto.framework.core.services.Impl.base;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import ncme.auto.framework.core.annotation.Remind;
import ncme.auto.framework.core.entities.service.SelectResult;
import ncme.auto.framework.core.exceptions.AutoException;
import ncme.auto.framework.core.exceptions.ExceptionEnum;
import ncme.auto.framework.core.utils.BeanUtil;
import ncme.auto.framework.core.utils.RedisUtil;
import ncme.auto.framework.core.utils.ServiceUtil;
import ncme.auto.framework.core.utils.StringUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public abstract class DatabaseServiceImpl<E> extends BaseReflectService<E> {

    private final boolean useRedis = true;
    @Autowired
    protected RedisUtil redisUtil;
    @Autowired
    public BaseMapper<E> mapper;

    /**
     * 查询数量
     *
     * @return 实体总数量
     */
    protected Integer selectCount() {
        Integer numInSQL = mapper.selectCount(null);
        Integer numInRedis = sGetValuesFromRedisKey(ENTITY_NAME_LOWER + "Ids").size();
        if (numInRedis.equals(numInSQL)) return numInRedis;
        else return getAllEntitiesToRedis().size();
    }

    /**
     * 根据查询条件Map、分页信息、是否模糊查询、是否倒序，获取数据
     *
     * @param searchMap 查询条件
     * @param page      页数
     * @param size      页码
     * @param isLike    是否模糊查询
     * @param reverse   是否倒序
     * @return 查询结果
     */
    protected SelectResult<E> select(Map<String, Object> searchMap, int page, int size, boolean isLike, boolean reverse) {
        try {
            Integer totalCount = selectCount();
            searchMap = filterSearchMap(searchMap);
            SelectResult<E> selectResult = new SelectResult<>();
            //无查询条件、单一查询条件（包括唯一键、组合md5及自定义键）、多条件直接走mysql
            if (useRedis && searchMap.size() == 0) {
                //验证分页参数
                if (page * size >= totalCount) return selectResult;
                //从redis获取ids
                Set<Object> entityIds = redisUtil.sGet(ENTITY_NAME_LOWER + "Ids", page, size, reverse);
                List<E> entities = new ArrayList<>();
                for (Object entityId : entityIds) entities.add((E) redisUtil.get(ENTITY_NAME_LOWER + "Id:" + entityId));
                selectResult.setPageData(entities);
                selectResult.setTotalNum(totalCount);
            } else if (!isLike && useRedis && searchMap.size() == 1 && !searchMap.containsKey("filterMap")) {
                String keyStr = toUpper(searchMap.keySet().iterator().next());
                Object valueObj = searchMap.values().iterator().next();
                if (redisUtil.hasKey(ENTITY_NAME_LOWER + keyStr + ":" + valueObj)) {
                    Object valueInRedis = redisUtil.get(ENTITY_NAME_LOWER + keyStr + ":" + valueObj);
                    if (valueInRedis != null) {
                        if (keyStr.equals("Id")) selectResult.addPageData((E) valueInRedis);
                        else selectResult.addPageData((E) redisUtil.get(ENTITY_NAME_LOWER + "Id:" + valueInRedis));
                        selectResult.setTotalNum(1);
                    }
                } else if (redisUtil.hasKey(ENTITY_NAME_LOWER + "ExtraSet" + keyStr + ":" + valueObj)) {
                    Set<Object> ids = redisUtil.sGet(ENTITY_NAME_LOWER + "ExtraSet" + keyStr + ":" + valueObj);
                    if (ids != null) {
                        for (Object id : ids) {
                            selectResult.addPageData((E) redisUtil.get(ENTITY_NAME_LOWER + "Id:" + id));
                        }
                        selectResult.setTotalNum(ids.size());
                    }
                } else if (redisUtil.hasKey(ENTITY_NAME_LOWER + "ExtraSingleVerify" + keyStr + ":" + valueObj)) {
                    Object valueInRedis = redisUtil.get(ENTITY_NAME_LOWER + "ExtraSingleVerify" + keyStr + ":" + valueObj);
                    selectResult.addPageData((E) valueInRedis);
                    selectResult.setTotalNum(1);
                } else if (keyStr.equals("Md5")) {
                    getAllEntitiesToRedis();
                    Object md5EntityId = redisUtil.get(ENTITY_NAME_LOWER + "Md5:" + valueObj);
                    if (md5EntityId == null) {
                        redisUtil.set(ENTITY_NAME_LOWER + "Md5:" + valueObj, null);
                        selectResult.setPageData(new ArrayList<>());
                        selectResult.setTotalNum(0);
                    } else {
                        Integer entityId = Integer.parseInt(md5EntityId.toString());
                        Map<String, Object> map = new HashMap<>();
                        map.put("id", entityId);
                        return select(map, 0, 1, false, false);
                    }
                } else {
                    List<E> es = mapper.selectList(getSearchWrapper(searchMap, isLike));
                    selectResult.setPageData(ServiceUtil.pageList(es, page, size));
                    selectResult.setTotalNum(es.size());
                }
            } else {
                List<E> es = mapper.selectList(getSearchWrapper(searchMap, isLike));
                //filter
                if (searchMap.containsKey("filterMap")) {
                    Map<String, Object> filterMap = (Map<String, Object>) searchMap.get("filterMap");
                    es = entityFilterByMap(es, filterMap);
                }
                selectResult.setPageData(ServiceUtil.pageList(es, page, size));
                selectResult.setTotalNum(es.size());
            }
            return selectResult;
        } catch (Exception e) {
            throw new AutoException(ExceptionEnum.JDBC, "DatabaseServiceImpl.select", e);
        }
    }

    /**
     * 插入实体
     *
     * @param entity 要插入的实体
     * @return 新增id
     */
    protected Integer insert(E entity) {
        try {
            int insertState = mapper.insert(entity);
            if (insertState == 1) {
                if (useRedis) addEntityToRedis(entity);
                return Integer.parseInt(getFieldValue(entity, "id").toString());
            } else return -1;
        } catch (Exception e) {
            throw new AutoException(ExceptionEnum.JDBC, "DatabaseServiceImpl.insert", e);
        }
    }

    /**
     * 更新实体，由于需要删除对应redis，需要传入新旧entity
     *
     * @param updateEntity 更新值后的实体对象
     * @param oldEntity    更新前的实体对象，用于删除redis对应键
     */
    protected void update(E updateEntity, E oldEntity) {
        try {
            Map<String, Object> entityMap = BeanUtil.beanToMap(updateEntity, Collections.singletonList("id"), true);
            if (entityMap.size() == 0) throw new AutoException(ExceptionEnum.PARAM_FIELD, "无可修改字段");
            Integer id = (Integer) getFieldValue(updateEntity, "id");
            int insertState = mapper.updateById(updateEntity);
            if (insertState == 1 && useRedis) {
                delEntityFromRedis(oldEntity);
                E entityAfterUpdate = mapper.selectById(id);
                addEntityToRedis(entityAfterUpdate);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new AutoException(ExceptionEnum.JDBC, "DatabaseServiceImpl.update", e);
        }
    }

    /**
     * 删除实体，由于需要删除对应redis，需要传入旧entity
     *
     * @param entity 需要删除的实体对象，需要包含完整信息，用于删除redis对应的值
     */
    protected void delete(E entity) {
        Integer delId = (Integer) getFieldValue(entity, "id");
        int delState = mapper.deleteById(delId);
        if (delState == 1) {
            if (useRedis) delEntityFromRedis(entity);
        } else throw new AutoException(ExceptionEnum.JDBC, "DatabaseServiceImpl.delete");
    }

    //################################################# redis-tool #################################################

    /**
     * 根据redisKey获取所有value的list
     *
     * @param redisKey redis键
     * @return 值集合
     */
    protected List<Object> sGetValuesFromRedisKey(String redisKey) {
        Set<Object> valueSet = redisUtil.sGet(redisKey);
        if (valueSet == null || valueSet.size() == 0) {
            getAllEntitiesToRedis();
            valueSet = redisUtil.sGet(redisKey);
        }
        return new ArrayList<>(valueSet);
    }

    /**
     * 根据redisKey获取所有entity的list
     *
     * @param redisKey redis键
     * @return 实体集合
     */
    protected List<E> sGetEntitiesFromRedisKey(String redisKey) {
        List<Object> values = sGetValuesFromRedisKey(redisKey);
        List<E> entities = new ArrayList<>();
        if (redisKey.equals(ENTITY_NAME_LOWER + "Id")) {
            values.forEach(value -> entities.add((E) value));
        } else {
            values.forEach(value -> entities.add((E) redisUtil.get(ENTITY_NAME_LOWER + "Id:" + value)));
        }
        return entities;
    }

    /**
     * 将实体对象的基本信息添加至 redis，包含：
     * entityId->entity
     * entityIds->entityId(set)
     * entityOnlyField->entityId
     * entityMultiFieldMd5->entityId
     * 有的可能用不到，但为了统一暂时都加了
     *
     * @param entity 实体对象
     */
    protected void addEntityToRedis(E entity) {
        //id
        Integer entityId = (Integer) getFieldValue(entity, "id");
        redisUtil.set(ENTITY_NAME_LOWER + "Id:" + entityId, entity);
        redisUtil.sSet(ENTITY_NAME_LOWER + "Ids", entityId);
        //唯一字段
        if (VERIFY_REPETITION_FIELD.size() != 0) {
            for (String fieldName : VERIFY_REPETITION_FIELD) {
                String fieldNameUpper = toUpper(fieldName);
                redisUtil.set(ENTITY_NAME_LOWER + fieldNameUpper + ":" + getFieldValue(entity, fieldName), entityId);
            }
        }
        //组合唯一字段
        if (VERIFY_MULTI_REPETITION_FIELD.size() != 0) {
            List<Object> fieldsValues = new ArrayList<>();
            for (String fieldName : VERIFY_MULTI_REPETITION_FIELD) {
                fieldsValues.add(getFieldValue(entity, fieldName));
            }
            String md5 = ServiceUtil.getMd5(fieldsValues);
            redisUtil.set(ENTITY_NAME_LOWER + "Md5:" + md5, entityId);
        }
        //额外的需要实现的加redis方法
        try {
            List<String> fieldsForExtraRedisSet = getFieldsForExtraRedisSet();
            if (fieldsForExtraRedisSet != null)
                for (String field : fieldsForExtraRedisSet) {
                    if (!FIELD_NAMES.contains(field)) throw new AutoException(ExceptionEnum.JDBC, "预制字段不存在");
                    redisUtil.sSet(ENTITY_NAME_LOWER + "ExtraSet" + toUpper(field) + ":" + getFieldValue(entity, field), entityId);
                }
            List<String> fieldsForExtraRedisSingleVerify = getFieldsForExtraRedisSingleVerify();
            if (fieldsForExtraRedisSingleVerify != null)
                for (String field : fieldsForExtraRedisSingleVerify) {
                    if (!FIELD_NAMES.contains(field)) throw new AutoException(ExceptionEnum.JDBC, "预制字段不存在");
                    Object fieldValue = getFieldValue(entity, field);
                    redisUtil.set(ENTITY_NAME_LOWER + "ExtraSingleVerify" + toUpper(field) + ":" + fieldValue, entityId);
                    redisUtil.sSet(ENTITY_NAME_LOWER + "ExtraSingleVerify" + toUpper(field) + "s:", fieldValue);
                }
            addEntityOtherToRedis(entity);
        } catch (Exception e) {

            throw new AutoException(ExceptionEnum.REDIS, "自定义加redis方法异常");
        }
    }

    /**
     * 对应添加 redis 的方法，将添加的键值进行相应删除
     *
     * @param entity 要删除的实体对象
     */
    protected void delEntityFromRedis(E entity) {
        //id
        Integer entityId = (Integer) getFieldValue(entity, "id");
        redisUtil.del(ENTITY_NAME_LOWER + "Id:" + entityId);
        redisUtil.setRemove(ENTITY_NAME_LOWER + "Ids", entityId);
        //唯一字段
        if (VERIFY_REPETITION_FIELD.size() != 0) {
            for (String fieldName : VERIFY_REPETITION_FIELD) {
                String fieldNameUpper = toUpper(fieldName);
                redisUtil.del(ENTITY_NAME_LOWER + fieldNameUpper + ":" + getFieldValue(entity, fieldName));
            }
        }
        //组合唯一字段
        if (VERIFY_MULTI_REPETITION_FIELD.size() != 0) {
            List<Object> fieldsValues = new ArrayList<>();
            for (String fieldName : VERIFY_MULTI_REPETITION_FIELD) {
                fieldsValues.add(getFieldValue(entity, fieldName));
            }
            String md5 = ServiceUtil.getMd5(fieldsValues);
            redisUtil.del(ENTITY_NAME_LOWER + "Md5:" + md5);
        }
        //额外的需要实现的删redis方法
        try {
            List<String> fieldsForExtraRedisSet = getFieldsForExtraRedisSet();
            if (fieldsForExtraRedisSet != null)
                for (String field : fieldsForExtraRedisSet) {
                    redisUtil.setRemove(ENTITY_NAME_LOWER + "ExtraSet" + field + ":" + getFieldValue(entity, field), entityId);
                }
            List<String> fieldsForExtraRedisSingleVerify = getFieldsForExtraRedisSingleVerify();
            if (fieldsForExtraRedisSingleVerify != null)
                for (String field : fieldsForExtraRedisSingleVerify) {
                    Object fieldValue = getFieldValue(entity, field);
                    redisUtil.del(ENTITY_NAME_LOWER + "ExtraSingleVerify" + field + ":" + fieldValue);
                    redisUtil.setRemove(ENTITY_NAME_LOWER + "ExtraSingleVerify" + field + "s:", fieldValue);
                }
            delEntityOtherFromRedis(entity);
        } catch (Exception e) {
            throw new AutoException(ExceptionEnum.REDIS, "自定义删redis方法异常");
        }
    }

    /**
     * 除了id、唯一字段、组合唯一字段，其他想要加入 redis 的需要实现此方法
     *
     * @param entity 待处理的是实体对象
     */
    public abstract void addEntityOtherToRedis(E entity);

    /**
     * 需要实现与添加 redis 方法对应的删除操作
     *
     * @param entity 待处理的是实体对象
     */
    public abstract void delEntityOtherFromRedis(E entity);

    @Remind("加入哪些字段进入 set 类型的redis" +
            "key 对应 该列表中的field，value对应 id" +
            "使用时super.getFieldsForExtraRedisSet 后再添加" +
            "该函数体内可预制字段")
    public abstract List<String> getFieldsForExtraRedisSet();

    @Remind("加入哪些字段 添加 单识别字段的redis操作" +
            "例如：实体中已有@VerifyMultiRepetitionField注解，但其中一个字段需要单独添加id和ids redis内容" +
            "使用时super.getFieldsForExtraRedisSingleVerify 后再添加" +
            "该函数体内可预制字段")
    public abstract List<String> getFieldsForExtraRedisSingleVerify();

    //################################################# mysql-tool #################################################

    /**
     * 将searchMap转换为queryMapper，另包含扩展条件
     *
     * @param searchMap 查询条件
     * @param isLike    是否模糊查询
     * @return mp查询条件
     */
    public QueryWrapper<E> getSearchWrapper(Map<String, Object> searchMap, boolean isLike) {
        QueryWrapper<E> queryWrapper = new QueryWrapper<>();
        if (searchMap == null) searchMap = new HashMap<>();
        for (String key : searchMap.keySet()) {
            Object value = searchMap.get(key);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            try {
                if (key.endsWith("BeginDate")) {
                    queryWrapper.ge(underscoreName(key.replace("BeginDate", "")), new Date(sdf.parse(searchMap.get(key).toString()).getTime()));
                    continue;
                } else if (key.endsWith("EndDate")) {
                    queryWrapper.le(underscoreName(key.replace("EndDate", "")), new Date(sdf.parse(searchMap.get(key).toString()).getTime() + 86400000));
                    continue;
                }
            } catch (Exception e) {
                throw new AutoException(ExceptionEnum.PARAM_FIELD, "日期格式出错，需要：yyyy-MM-dd", e);
            }
            try {
                if (key.endsWith("Min")) {
                    queryWrapper.ge(underscoreName(key.replace("Min", "")), searchMap.get(key).toString());
                    continue;
                } else if (key.endsWith("Max")) {
                    queryWrapper.le(underscoreName(key.replace("Max", "")), searchMap.get(key).toString());
                    continue;
                }
            } catch (Exception e) {
                throw new AutoException(ExceptionEnum.PARAM_FIELD, "Min/Max标识的字段不可比较", e);
            }
            if (FIELD_NAMES.contains(key)) {
                if (isLike) queryWrapper.like(underscoreName(key), searchMap.get(key));
                else queryWrapper.eq(underscoreName(key), value);
            }
        }
        return queryWrapper;
    }

    /**
     * 查询条件预处理，设置忽略字段
     *
     * @param searchMap 查询条件
     * @return 处理过的查询条件
     */
    public Map<String, Object> filterSearchMap(Map<String, Object> searchMap) {
        Map<String, Object> validSearchMap = new HashMap<>();
        if (searchMap != null) {
            for (String key : searchMap.keySet()) {
                Object value = searchMap.get(key);
                String[] ignoreKeys = new String[]{"page", "size", "isDeleted", "version"};
                if (value == null || StringUtil.isEmpty(value.toString()) || ArrayUtils.contains(ignoreKeys, key))
                    continue;
                validSearchMap.put(key, value);
            }
        }
        return validSearchMap;
    }

    /**
     * 查询结果过滤
     */
    private List<E> entityFilterByMap(List<E> originList, Map<String, Object> filterMap) {
        List<E> results = new ArrayList<>();
        try {
            for (E entity : originList) {
                boolean retain = true;
                for (String key : filterMap.keySet()) {
                    Object value = BeanUtil.getFieldValue(entity, key);
                    Object condition = filterMap.get(key);
                    if (condition instanceof Collection) {
                        retain = ((Collection<?>) condition).contains(value);
                    } else if (condition instanceof Object[]) {
                        retain = ArrayUtils.contains((Object[]) condition, value);
                    } else {
                        retain = condition.equals(value);
                    }
                }
                if (retain) results.add(entity);
            }
        } catch (Exception e) {
            throw new AutoException("查询结果过滤异常，请检查过滤参数名是否与实体字段名一致", e);
        }
        return results;
    }

    /**
     * 将数据库中的内容刷入redis
     *
     * @return 所有实体集合
     */
    private List<E> getAllEntitiesToRedis() {
        System.out.println("除非清了redis，否则不应该进到这里@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        List<E> entities;
        try {
            entities = mapper.selectList(null);
        } catch (Exception e) {
            throw new AutoException(ExceptionEnum.JDBC, "数据库查询失败，@getAllEntitiesToRedis()，+ ", e); //检查实体类有没有空构造方法
        }
        redisUtil.getKeysBeginWith(ENTITY_NAME_LOWER + "*").forEach(redisUtil::del);
        for (E entity : entities) {
            addEntityToRedis(entity);
        }
        return entities;
    }

}
